package online.heycm.rbac.service.user.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import online.heycm.platform.common.entity.result.Optional;
import online.heycm.platform.common.entity.result.PageResult;
import online.heycm.rbac.common.cache.RoleCache;
import online.heycm.rbac.common.constant.ErrorCode;
import online.heycm.rbac.common.converter.ConvertHelper;
import online.heycm.rbac.common.converter.RbacConverter;
import online.heycm.rbac.common.entity.Session;
import online.heycm.rbac.common.type.AuthType;
import online.heycm.rbac.common.uid.UidKey;
import online.heycm.rbac.common.uid.UidService;
import online.heycm.rbac.common.utils.Md5Util;
import online.heycm.rbac.common.utils.NicknameUtil;
import online.heycm.rbac.domain.basic.repository.RbacRepository;
import online.heycm.rbac.domain.basic.repository.entity.BasicRbacResource;
import online.heycm.rbac.domain.user.Authenticator;
import online.heycm.rbac.domain.user.aggregate.ConsoleUser;
import online.heycm.rbac.domain.user.repository.UserRepository;
import online.heycm.rbac.domain.user.repository.entity.UserAuthorize;
import online.heycm.rbac.domain.user.repository.entity.UserProfile;
import online.heycm.rbac.param.user.ConsoleLoginReq;
import online.heycm.rbac.param.user.ConsoleResourceRes;
import online.heycm.rbac.param.user.UserAddReq;
import online.heycm.rbac.param.user.UserDetailRes;
import online.heycm.rbac.param.user.UserEditReq;
import online.heycm.rbac.param.user.UserPageReq;
import online.heycm.rbac.param.user.UserPageRes;
import online.heycm.rbac.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.StringUtils;

/**
 * @author hey
 * @version 1.0
 * @date 2024/1/12 17:09
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private Authenticator authenticator;

    @Autowired
    private RbacRepository rbacRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UidService uidService;

    @Override
    public Optional logout(Session session) {
        return authenticator.expire(session);
    }

    @Override
    public Optional<Session> consoleUserLogin(ConsoleLoginReq req) {
        AuthType authType = AuthType.of(req.getAuthType());
        if (authType == AuthType.ACCOUNT) {
            return authenticator.account(req.getAuthIdentifier(), req.getAuthCredential());
        }
        return Optional.error(ErrorCode.LOGIN_AUTH_TYPE);
    }

    @Override
    public Optional<List<ConsoleResourceRes>> consoleUserResource(Session session) {
        List<BasicRbacResource> resources = rbacRepository.roleResources(session.getRoleIds());
        List<BasicRbacResource> parent = resources.stream().filter(e -> e.getPid().equals(0)).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(parent)) {
            return Optional.of(Collections.emptyList());
        }
        List<ConsoleResourceRes> convertParent = ConvertHelper.list(parent, RbacConverter.INST::convert);
        deepFind(convertParent, resources);
        return Optional.of(convertParent);
    }

    private void deepFind(List<ConsoleResourceRes> parent, List<BasicRbacResource> resources) {
        for (ConsoleResourceRes p : parent) {
            List<BasicRbacResource> children = resources.stream().filter(e -> e.getPid().equals(p.getResourceId())).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(children)) {
                p.setChildren(ConvertHelper.list(children, RbacConverter.INST::convert));
                deepFind(p.getChildren(), resources);
            } else {
                p.setChildren(Collections.emptyList());
            }
        }
    }

    @Override
    public Optional<PageResult<UserPageRes>> pageUser(UserPageReq req) {
        IPage<UserProfile> page = userRepository.pageUser(req);
        PageResult<UserPageRes> result = ConvertHelper.page(page, RbacConverter.INST::convert);
        return Optional.of(result);
    }

    @Override
    public Optional<UserDetailRes> getUserDetail(int userId) {
        Optional<ConsoleUser> optional = userRepository.getUserDetail(userId);
        if (optional.fail()) {
            return Optional.error(optional);
        }
        ConsoleUser consoleUser = optional.get();
        UserDetailRes userDetailRes = RbacConverter.INST.convertUserDetailRes(consoleUser.getUserProfile());
        Optional.ofNullable(consoleUser.getUserAuthorize()).ifPresent(auth -> userDetailRes.setLoginTime(auth.getLoginTime()));
        Optional.ofNullable(consoleUser.getRoles()).ifPresent(roles -> userDetailRes.setRoles(ConvertHelper.list(roles, RbacConverter.INST::convert)));
        return Optional.of(userDetailRes);
    }

    @Override
    public Optional addUser(UserAddReq req) {
        UserProfile profile = RbacConverter.INST.convert(req);
        profile.setUserId((int) uidService.nextId(UidKey.USER_ID));
        profile.setNickname(NicknameUtil.generate());
        UserAuthorize authorize = new UserAuthorize();
        authorize.setUserId(profile.getUserId());
        authorize.setAuthType(1);
        authorize.setAuthIdentifier(req.getPhone());
        String salt = Md5Util.generateSalt();
        String encrypted = Md5Util.encrypt(req.getAuthCredential(), salt);
        authorize.setAuthCredential(encrypted);
        authorize.setAuthSalt(salt);
        ConsoleUser consoleUser = new ConsoleUser(profile, authorize);
        for (Integer roleId : req.getRoleIds()) {
            consoleUser.addRole(RoleCache.getById(roleId));
        }
        return userRepository.addUser(consoleUser);
    }

    @Override
    public Optional editUser(UserEditReq req) {
        UserProfile profile = RbacConverter.INST.convert(req);
        UserAuthorize authorize = new UserAuthorize();
        authorize.setUserId(profile.getUserId());
        authorize.setAuthType(1);
        authorize.setAuthIdentifier(req.getPhone());
        if (StringUtils.hasText(req.getAuthCredential())) {
            String salt = Md5Util.generateSalt();
            String encrypted = Md5Util.encrypt(req.getAuthCredential(), salt);
            authorize.setAuthCredential(encrypted);
            authorize.setAuthSalt(salt);
        }
        ConsoleUser consoleUser = new ConsoleUser(profile, authorize);
        for (Integer roleId : req.getRoleIds()) {
            consoleUser.addRole(RoleCache.getById(roleId));
        }
        return userRepository.editUser(consoleUser);
    }
}
